• ABOUT
  • 2018
  • 2019
  • 2020
  • 2021
  • 2022
  • COMPARAISON
  • WIDGET
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sbn
from urllib.request import urlopen
import squarify
import folium
import json
import plotly.express as px
import warnings
warnings.filterwarnings('ignore')

Importation des datasets¶

In [2]:
Data = pd.read_csv('valeursfoncieres-2018.txt', sep='|')

Visualisation du dataset non nettoyé¶

In [3]:
Data
Out[3]:
Identifiant de document Reference document 1 Articles CGI 2 Articles CGI 3 Articles CGI 4 Articles CGI 5 Articles CGI No disposition Date mutation Nature mutation ... Surface Carrez du 5eme lot Nombre de lots Code type local Type local Identifiant local Surface reelle bati Nombre pieces principales Nature culture Nature culture speciale Surface terrain
0 NaN NaN NaN NaN NaN NaN NaN 1 03/01/2018 Vente ... NaN 2 2.0 Appartement NaN 73.0 4.0 NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN 1 03/01/2018 Vente ... NaN 1 3.0 Dépendance NaN 0.0 0.0 NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN 1 04/01/2018 Vente ... NaN 0 1.0 Maison NaN 163.0 4.0 S NaN 949.0
3 NaN NaN NaN NaN NaN NaN NaN 1 04/01/2018 Vente ... NaN 0 1.0 Maison NaN 163.0 4.0 AG JARD 420.0
4 NaN NaN NaN NaN NaN NaN NaN 1 04/01/2018 Vente ... NaN 0 1.0 Maison NaN 51.0 2.0 AG JARD 420.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3335287 NaN NaN NaN NaN NaN NaN NaN 1 28/12/2018 Vente ... NaN 2 2.0 Appartement NaN 150.0 4.0 NaN NaN NaN
3335288 NaN NaN NaN NaN NaN NaN NaN 1 03/12/2018 Vente ... NaN 1 2.0 Appartement NaN 34.0 1.0 NaN NaN NaN
3335289 NaN NaN NaN NaN NaN NaN NaN 1 28/12/2018 Vente ... NaN 1 4.0 Local industriel. commercial ou assimilé NaN 60.0 0.0 NaN NaN NaN
3335290 NaN NaN NaN NaN NaN NaN NaN 1 13/12/2018 Adjudication ... NaN 2 2.0 Appartement NaN 54.0 2.0 NaN NaN NaN
3335291 NaN NaN NaN NaN NaN NaN NaN 1 17/10/2018 Vente ... NaN 1 2.0 Appartement NaN 11.0 1.0 NaN NaN NaN

3335292 rows × 43 columns

Nettoyage de la donnée¶

In [4]:
columns_to_keep = ['Date mutation','Nature mutation','Valeur fonciere','Code postal','Commune','Code departement','Code commune','Nombre de lots','Code type local','Type local','Surface reelle bati','Nombre pieces principales','Surface terrain']
Data['Date mutation'] = pd.to_datetime(Data['Date mutation'])
Data['Code departement'] = Data['Code departement'].astype(str)
Data = Data[columns_to_keep]

Data = Data.dropna()
Data['Valeur fonciere'] = pd.to_numeric(Data['Valeur fonciere'].str.replace(',', '.'))
Data
Out[4]:
Date mutation Nature mutation Valeur fonciere Code postal Commune Code departement Code commune Nombre de lots Code type local Type local Surface reelle bati Nombre pieces principales Surface terrain
2 2018-04-01 Vente 239300.0 1250.0 NIVIGNE ET SURAN 1 95 0 1.0 Maison 163.0 4.0 949.0
3 2018-04-01 Vente 239300.0 1250.0 NIVIGNE ET SURAN 1 95 0 1.0 Maison 163.0 4.0 420.0
4 2018-04-01 Vente 239300.0 1250.0 NIVIGNE ET SURAN 1 95 0 1.0 Maison 51.0 2.0 420.0
5 2018-04-01 Vente 239300.0 1250.0 NIVIGNE ET SURAN 1 95 0 1.0 Maison 51.0 2.0 949.0
7 2018-04-01 Vente 90000.0 1380.0 SAINT-CYR-SUR-MENTHON 1 343 0 1.0 Maison 150.0 3.0 347.0
... ... ... ... ... ... ... ... ... ... ... ... ... ...
3335253 2018-12-19 Vente 4026000.0 75002.0 PARIS 02 75 102 0 2.0 Appartement 40.0 3.0 158.0
3335254 2018-12-19 Vente 4026000.0 75002.0 PARIS 02 75 102 0 2.0 Appartement 18.0 1.0 158.0
3335255 2018-12-19 Vente 4026000.0 75002.0 PARIS 02 75 102 0 2.0 Appartement 8.0 1.0 158.0
3335256 2018-12-21 Vente 5500000.0 75002.0 PARIS 02 75 102 0 4.0 Local industriel. commercial ou assimilé 498.0 0.0 109.0
3335268 2018-12-21 Vente 1500000.0 75002.0 PARIS 02 75 102 0 4.0 Local industriel. commercial ou assimilé 485.0 0.0 152.0

897263 rows × 13 columns

Argent total dépensé par mois selon les types de mutation pendant l'année¶

In [5]:
MUTATIONS = Data['Nature mutation'].unique()
def plotMutations(mut, data, ax):

    for m in MUTATIONS:
        temp = data[data['Nature mutation'] == m]
        result = temp.groupby(temp['Date mutation'].dt.to_period("M"))['Valeur fonciere'].sum()
        result.index = result.index.to_timestamp()
        x = result.index
        y = result.values
        
        if m == mut:
            ax.plot(x, y, color="#0b53c1", lw=2.4, zorder=10)
            ax.scatter(x, y, fc="w", ec="#0b53c1", s=60, lw=2.4, zorder=12)
            ax.autoscale()    
        else:
            ax.plot(x, y, color="#BFBFBF", lw=1.5)
    
    ax.set_title(mut, fontfamily="DejaVu Sans", fontsize=14, fontweight=500)
    return ax
In [6]:
fig, axes = plt.subplots(2, 3, figsize=(14, 7.5))
for idx, (ax, mut) in enumerate(zip(axes.ravel(), MUTATIONS)):
    annotate = idx == 0
    plotMutations(mut, Data, ax)

On remarque que essentiellement le type de mutation sont des ventes¶

Autre visualisation du phénomène¶

In [7]:
data1 = Data[Data['Nature mutation'] =='Vente'] 
data1 = data1.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data2 = Data[Data['Nature mutation'] =='Vente terrain à bâtir'] 
data2 = data2.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data3 = Data[Data['Nature mutation'] =='Echange'] 
data3 = data3.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data4 = Data[Data['Nature mutation'] =="Vente en l'état futur d'achèvement"] 
data4 = data4.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data5 = Data[Data['Nature mutation'] =='Adjudication'] 
data5 = data5.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data6 = Data[Data['Nature mutation'] =='Expropriation'] 
data6 = data6.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


plt.figure(figsize=(18,11))
plt.plot(data1.index, data1.values, "r--", color="red")
plt.plot(data2.index, data2.values, "r--", color="blue")
plt.plot(data3.index, data3.values, "r--", color="green")
plt.plot(data4.index, data4.values, "r--", color="yellow")
plt.plot(data5.index, data5.values, "r--", color="purple")
plt.plot(data6.index, data6.values, "r--", color="black")
plt.legend(['Vente','Vente terrain à bâtir', 'Echange',"Vente en l'état futur d'achèvement",'Adjudication','Expropriation'])
plt.title('Nombre de mutations par type au cours des mois, en cumulé')
plt.show()

Cela confirme encore que le type de mutation importante est la vente¶

Nombre et répartitions des différents types¶

In [8]:
df = Data.groupby(['Type local'])['Type local'].count()

plt.bar(df.index, df.values)
bars = ['Appartement', 'Dépendance', 'Industriel', 'Maison']
y_pos = np.arange(len(bars))
plt.xticks(y_pos, bars)
plt.title('Nombre de mutations par type')
Out[8]:
Text(0.5, 1.0, 'Nombre de mutations par type')
In [9]:
fig, ax = plt.subplots()
ax.pie(df, labels=df.index, autopct='%1.1f%%')
ax.set_title('Proportion des types de locaux sur le nombre total de mutations')

plt.show()

Répartition des types de biens suivant différents caractères¶

In [11]:
df=Data[Data["Surface terrain"]< 5000]
plt.figure(figsize=(18,10))
plt.xticks(rotation=25)
sbn.violinplot(x = "Type local",y="Surface terrain", data=df)
plt.title('Répartition des types de locaux selon la surface de leur terrain')
Out[11]:
Text(0.5, 1.0, 'Répartition des types de locaux selon la surface de leur terrain')
In [12]:
df=Data[(Data["Surface reelle bati"]< 1000) & (Data["Surface reelle bati"].notna())]
plt.figure(figsize=(18,10))
plt.xticks(rotation=25)
sbn.violinplot(x = "Type local",y="Surface reelle bati", data=df)
plt.title('Répartition des types de locaux selon leur surface réelle bâtie')
Out[12]:
Text(0.5, 1.0, 'Répartition des types de locaux selon leur surface réelle bâtie')
In [13]:
df = Data[Data['Valeur fonciere'] < 2000000]
plt.figure(figsize=(18,10))
plt.xticks(rotation=25)
sbn.violinplot(x="Type local",y="Valeur fonciere",data=df)
plt.title('Répartition des types de locaux selon leur valeur foncière')
Out[13]:
Text(0.5, 1.0, 'Répartition des types de locaux selon leur valeur foncière')

Analyse des données par département¶

In [15]:
df = Data.groupby(['Code departement'])['Nature mutation'].count().sort_values(ascending=True)
plt.figure(figsize=(10,20))

plt.hlines(y=df.index, xmin=0, xmax=df.values, color='black')
plt.plot(df.values, df.index, "o", color="blue")
 

    
plt.title('Nombre de mutations par département')
plt.xlabel('Nombre de mutations')
plt.ylabel('Numéros de départements')
plt.show()
In [16]:
df1 = Data.groupby(['Code departement'])['Nature mutation'].count()

df = Data[((Data['Nature mutation']=='Vente') & ((Data['Type local'] == 'Maison') | (Data['Type local'] == 'Appartement')))]
df = Data.groupby(['Code departement'])['Nature mutation'].count()
plt.figure(figsize=(10,20))

plt.hlines(y=df1.index, xmin = 0, xmax = df1.values, color='red')
plt.hlines(y=df.index, xmin=0, xmax=df.values, color='skyblue')
plt.plot(df.values, df.index, "o")
plt.plot(df1.values, df1.index, "x", color="white")

plt.title("Nombre de mutations par département (ronds) et nombre de mutations par département en considérant uniquement les ventes d'appartements et de maisons (croix)")
plt.xlabel('Nombre de mutations')
plt.ylabel('Numéros de départements')

plt.show()

On observe que les maisons et appartements constitute la majeure partie des mutations¶

In [17]:
df_filtered = Data.loc[(Data['Nature mutation'] == 'Vente') & (Data['Type local'].isin(['Maison', 'Appartement']))]
df_mutations = df_filtered.groupby(['Code departement'])['Nature mutation'].count().reset_index()
df_mutations = df_mutations.sort_values(by='Nature mutation', ascending=False)
df_filtered2 = df_filtered.groupby(['Code departement', 'Type local'])['Type local'].count().unstack()
df_filtered2 = df_filtered2.fillna(0)
df_filtered2['Total'] = df_filtered2.sum(axis=1)
df_filtered2['% Maison/Appartement'] = ((df_filtered2['Maison'] + df_filtered2['Appartement']) / df_filtered2['Total']) * 100

# Trier les départements par pourcentage de ventes d'appartements et de maisons décroissant
df_filtered2 = df_filtered2.sort_values(by='% Maison/Appartement', ascending=False)

# Créer le graphique
fig, ax = plt.subplots(figsize=(18, 12))
sbn.barplot(x='% Maison/Appartement', y=df_filtered2.index, data=df_filtered2, color='skyblue')
ax.set_title("Pourcentage de ventes d'appartements et de maisons par département")
ax.set_xlabel('% Maison/Appartement')
ax.set_ylabel('Numéro de département')

plt.show()
In [19]:
myscale = None

def mapping_france_folium(data):
    map = folium.Map(location=[48.862, 2.346], zoom_start = 5)
    departments = f"https://france-geojson.gregoiredavid.fr/repo/departements.geojson"
    d = {'Code': data.index, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)

    folium.Choropleth(geo_data=departments, 
    data=da, 
    columns=['Code', 'Valeur'], 
    key_on='properties.code',
    fill_color= "PuRd",
    fill_opacity=1,
    line_opacity=.1).add_to(map)
    
    folium.LayerControl().add_to(map)
    return map

def mapping_Paris_circle(data, bigNumbers = False):
    map = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
    arr = json.load(open("arrondissements.geojson"))
    d = {'Code': data.index, 'Valeur': data.values}
    da = pd.DataFrame(d)
    for a in arr["features"]:
        prop = a["properties"]
        temp = da[da['Code'] == prop["c_arinsee"] - 100]
        temp = temp['Valeur'].values
        folium.Circle(prop["geom_x_y"], 
        fill=True,
        popup = prop["l_ar"],
        radius = (temp[0]/1) if not bigNumbers else temp[0]/9000000).add_to(map)
    return map


def mapping_Paris(data):
    map = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
    arr = json.load(open("arrondissements.geojson"))
    d = {'Code': data.index + 100, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)
    da = da[(da['Code'] >= 75100) & (da['Code'] <= 75120)]
    myscale = np.linspace(da['Valeur'].min(), da['Valeur'].max(), 10)
    folium.Choropleth(geo_data=arr, 
            data=da, 
            columns=['Code', 'Valeur'], 
            key_on='properties.c_arinsee',
            fill_color= "PuRd",
            threshold_scale=myscale,
            fill_opacity=0.8,
            line_opacity=.1).add_to(map)
        
    
    folium.LayerControl().add_to(map)
    return map

def mapping_Lyon(data):
    map = folium.Map(location = [45.763420, 4.834277], zoom_start = 12)
    arr = json.load(open("lyon.json"))
    d = {'Code': data.index + 380, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)
    da = da[(da['Code'] >= 69381) & (da['Code'] <= 69389)]
    folium.Choropleth(geo_data=arr, 
            data=da, 
            columns=['Code', 'Valeur'], 
            key_on='properties.insee',
            fill_color= "PuRd",
            threshold_scale=myscale,
            fill_opacity=0.8,
            line_opacity=.1).add_to(map)
    
    folium.LayerControl().add_to(map)
    return map

def mapping_Marseille(data):
    map = folium.Map(location = [43.296482, 5.36978], zoom_start = 12)
    arr = json.load(open("marseille.geojson"))
    d = {'Code': data.index + 200, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)
    da = da[(da['Code'] >= 13201) & (da['Code'] <= 13216)]
    da['Code'] = da['Code'].astype(int).astype(str)
    folium.Choropleth(geo_data=arr, 
            data=da, 
            columns=['Code', 'Valeur'], 
            key_on='properties.DEPCO',
            fill_color= "PuRd",
            threshold_scale=myscale,
            fill_opacity=0.8,
            line_opacity=.1).add_to(map)
    
    folium.LayerControl().add_to(map)
    return map
In [20]:
data = Data.groupby(['Code departement'])['Nature mutation'].count()
map = mapping_france_folium(data)
map
Out[20]:
Make this Notebook Trusted to load map: File -> Trust Notebook

On voit ici, en échelle logarithmique, le nombre de mutations par département au cours de l'année.¶

In [21]:
data = Data[Data['Nature mutation'] == 'Vente'].groupby(['Code departement'])['Valeur fonciere'].sum()
map = mapping_france_folium(data)
map
Out[21]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Valeur cumulée des ventes par département en échelle logarithmique¶

In [22]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data[data['Type local'] == 'Maison'].groupby(['Code departement'])['Valeur fonciere'].sum()
map = mapping_france_folium(data)
map
Out[22]:
Make this Notebook Trusted to load map: File -> Trust Notebook

On voit ci-dessus, en échelle logarithmique, la valeur cumulée des ventes de maisons par département au cours de l'année. Il est intéressant de noter que l'importance de Paris dans la carte précédente disparaît : très peu de maisons sont vendues à Paris même.¶

In [23]:
data = Data[(Data['Nature mutation'] == 'Vente') & ((Data['Type local'] == 'Maison') | (Data['Type local'] == 'Appartement'))]
data['prix_m2'] = data['Valeur fonciere']/data['Surface reelle bati']
data = data.groupby(['Code departement'])['prix_m2'].agg('mean')
map = mapping_france_folium(data)
map
Out[23]:
Make this Notebook Trusted to load map: File -> Trust Notebook

On voit ci-dessus, en échelle logarithmique, le prix au m2 par département.¶

In [24]:
data = Data[['Surface reelle bati','Valeur fonciere']]
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(18,10))

# Premier graphique avec échelle logarithmique
ax1.set_xscale('log')
ax1.set_yscale('log')
ax1.scatter(data['Surface reelle bati'],data['Valeur fonciere'])
ax1.set_title('Répartition de la valeur foncière en fonction de la surface bâtie (échelle logarithmique)')
ax1.set_xlabel('Surface bâtie')
ax1.set_ylabel('Valeur foncière')

# Deuxième graphique sans échelle logarithmique
ax2.scatter(data['Surface reelle bati'],data['Valeur fonciere'])
ax2.set_title('Répartition de la valeur foncière en fonction de la surface bâtie')
ax2.set_xlabel('Surface bâtie')
ax2.set_ylabel('Valeur foncière')

plt.show()

On observe bien que l'apllication du log permet mieux gérer ces valeurs extremes¶

Analyse de Paris¶

Mutation par arrondissement¶

In [25]:
data = Data.groupby(['Code postal'])['Nature mutation'].count()
map = mapping_Paris_circle(data, False)
map
Out[25]:
Make this Notebook Trusted to load map: File -> Trust Notebook

En échelle logarithmique¶

In [26]:
data = Data.groupby(['Code postal'])['Nature mutation'].count()
map = mapping_Paris(data)
map
Out[26]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Valeur des ventes par arrondissement¶

In [27]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Paris_circle(data, True)
map
Out[27]:
Make this Notebook Trusted to load map: File -> Trust Notebook
On observe que dans le 12 ème la valeur des ventes est bien supérieure aux autres arrondissement, en échelle logarithimique ci-dessous cela se quantifie plus facilement¶

On représente les mêmes données sur échelle logarithmique¶

In [28]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Paris(data)
map
Out[28]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Comparons ces résultas à Lyon et marseille¶

In [29]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Marseille(data)
map
Out[29]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [30]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Lyon(data)
map
Out[30]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Analyse de corrélation entre les caractéristiques foncières en Ile-de-France¶

In [31]:
data = Data[Data['Code departement'] == '75']
data = data[['Valeur fonciere','Surface reelle bati','Surface terrain', 'Nombre pieces principales']]
sbn.heatmap(data.corr(), annot= True, cmap='Reds')
plt.xticks(rotation = 45)
plt.title('Corrélation entre la valeur foncière, la surface réelle bâtie, \nla surface du terrain et le nombre de pièces principales\n(en IDF))')
plt.show()
In [ ]: